package org.springside.core.dao;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.beanutils.PropertyUtils;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.impl.CriteriaImpl;
import org.hibernate.metadata.ClassMetadata;
import org.springframework.dao.DataAccessException;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springside.core.utils.BeanUtils;

@SuppressWarnings({"unchecked","rawtypes"})
public class HibernateGenericDao extends HibernateDaoSupport
{
  public <T> T get(Class<T> entityClass, Serializable id)
  {
    return (T) getHibernateTemplate().get(entityClass, id);
  }

  public <T> List<T> getAll(Class<T> entityClass)
  {
    return getHibernateTemplate().loadAll(entityClass);
  }

public <T> List<T> getAll(Class<T> entityClass, String orderBy, boolean isAsc)
  {
    Assert.hasText(orderBy);
    if (isAsc) {
      return getHibernateTemplate().findByCriteria(
        DetachedCriteria.forClass(entityClass).addOrder(Order.asc(orderBy)));
    }
    return getHibernateTemplate().findByCriteria(
      DetachedCriteria.forClass(entityClass).addOrder(Order.desc(orderBy)));
  }

  public void save(Object o)
  {
    getHibernateTemplate().save(o);
  }

  public void remove(Object o)
  {
    getHibernateTemplate().delete(o);
  }

  public <T> void removeById(Class<T> entityClass, Serializable id)
  {
    remove(get(entityClass, id));
  }

  public void flush() {
    getHibernateTemplate().flush();
  }

  public void clear() {
    getHibernateTemplate().clear();
  }

  public Query createQuery(String hql, Object[] values)
  {
    Assert.hasText(hql);
    Query query = getSession().createQuery(hql);
    for (int i = 0; i < values.length; i++) {
      query.setParameter(i, values[i]);
    }
    return query;
  }

  public Query getNamedQuery(String namedQuery)
  {
    Assert.hasText(namedQuery);
    Query query = getSession().getNamedQuery(namedQuery);
    return query;
  }

  public <T> Criteria createCriteria(Class<T> entityClass, Criterion[] criterions)
  {
    Criteria criteria = getSession().createCriteria(entityClass);
    for (Criterion c : criterions) {
      criteria.add(c);
    }
    return criteria;
  }

  public <T> Criteria createCriteria(Class<T> entityClass, String orderBy, boolean isAsc, Criterion[] criterions)
  {
    Assert.hasText(orderBy);

    Criteria criteria = createCriteria(entityClass, criterions);

    if (isAsc)
      criteria.addOrder(Order.asc(orderBy));
    else {
      criteria.addOrder(Order.desc(orderBy));
    }
    return criteria;
  }

public List find(String hql, Object[] values)
  {
    Assert.hasText(hql);
    return getHibernateTemplate().find(hql, values);
  }

  public <T> List<T> findBy(Class<T> entityClass, String propertyName, Object value)
  {
    Assert.hasText(propertyName);
    return createCriteria(entityClass, new Criterion[] { Restrictions.eq(propertyName, value) }).list();
  }

  public <T> List<T> findBy(Class<T> entityClass, String propertyName, Object value, String orderBy, boolean isAsc)
  {
    Assert.hasText(propertyName);
    Assert.hasText(orderBy);
    return createCriteria(entityClass, orderBy, isAsc, new Criterion[] { Restrictions.eq(propertyName, value) }).list();
  }

  public <T> T findUniqueBy(Class<T> entityClass, String propertyName, Object value)
  {
    Assert.hasText(propertyName);
    return (T) createCriteria(entityClass, new Criterion[] { Restrictions.eq(propertyName, value) }).uniqueResult();
  }

  public Page pagedQuery(String hql, int startIndex, int pageSize, Object[] values)
  {
    Assert.hasText(hql);

    if (startIndex < 0) {
      startIndex = 0;
    }
    String countQueryString = " select count (*) " + removeSelect(removeOrders(hql));
    System.out.println(countQueryString);
    List countlist = getHibernateTemplate().find(countQueryString, values);
    long totalCount = Long.parseLong(countlist.get(0).toString());

    if (totalCount < 1L) {
      return new Page(new ArrayList(0), 0L, pageSize, startIndex);
    }

    Query query = createQuery(hql, values);
    List list = query.setFirstResult(startIndex).setMaxResults(pageSize).list();

    return new Page(list, totalCount, pageSize, startIndex);
  }

  public Page pagedQuery(List list, int startIndex, int pageSize)
  {
    if (startIndex < 0) {
      startIndex = 0;
    }
    long totalCount = new Long(list.size()).longValue();
    if (totalCount < 1L)
      return new Page(new ArrayList(0), 0L, pageSize, startIndex);
    int lastIndex = startIndex + pageSize;
    if (lastIndex > list.size()) {
      lastIndex = list.size();
    }
    if (startIndex > list.size()) {
      startIndex = 0;
    }
    return new Page(list.subList(startIndex, lastIndex), totalCount, pageSize, startIndex);
  }

  public Page pagedQuery(Criteria criteria, int startIndex, int pageSize)
  {
    if (startIndex < 0) {
      startIndex = 0;
    }
    Assert.notNull(criteria);
    CriteriaImpl impl = (CriteriaImpl)criteria;

    Projection projection = impl.getProjection();
    List orderEntries;
    try
    {
       orderEntries = (List)BeanUtils.forceGetProperty(impl, "orderEntries");
      BeanUtils.forceSetProperty(impl, "orderEntries", new ArrayList());
    } catch (Exception e) {
      throw new InternalError(" Runtime Exception impossibility throw ");
    }
    int totalCount = ((Integer)criteria.setProjection(Projections.rowCount()).uniqueResult()).intValue();

    criteria.setProjection(projection);
    if (projection == null) {
      criteria.setResultTransformer(CriteriaSpecification.ROOT_ENTITY);
    }
    try
    {
      BeanUtils.forceSetProperty(impl, "orderEntries", orderEntries);
    } catch (Exception e) {
      throw new InternalError(" Runtime Exception impossibility throw ");
    }

    if (totalCount < 1) {
      return new Page(new ArrayList(0), 0L, pageSize, startIndex);
    }

    List list = criteria.setFirstResult(startIndex).setMaxResults(pageSize).list();
    return new Page(list, totalCount, pageSize, startIndex);
  }

  public Page pagedQuery(Class entityClass, int startIndex, int pageSize, Criterion[] criterions)
  {
    if (startIndex < 0) {
      startIndex = 0;
    }
    Criteria criteria = createCriteria(entityClass, criterions);
    return pagedQuery(criteria, startIndex, pageSize);
  }

  public Page pagedQuery(Class entityClass, int startIndex, int pageSize, String orderBy, boolean isAsc, Criterion[] criterions)
  {
    if (startIndex < 0) {
      startIndex = 0;
    }
    Criteria criteria = createCriteria(entityClass, orderBy, isAsc, criterions);
    return pagedQuery(criteria, startIndex, pageSize);
  }

  public Page pagedNamedQuery(String queryName, int startIndex, int pageSize, Object[] values)
  {
    if (startIndex < 0) {
      startIndex = 0;
    }
    Assert.hasText(queryName);
    Query query = getSession().getNamedQuery(queryName);
    String hql = query.getQueryString();

    String countQueryString = " select count (*) " + removeSelect(removeOrders(hql));
    System.out.println(countQueryString);
    List countlist = getHibernateTemplate().find(countQueryString, values);
    long totalCount = Long.parseLong(countlist.get(0).toString());

    if (totalCount < 1L) {
      return new Page(new ArrayList(0), 0L, pageSize, startIndex);
    }

    List list = query.setFirstResult(startIndex).setMaxResults(pageSize).list();

    return new Page(list, totalCount, pageSize, startIndex);
  }

  public <T> boolean isUnique(Class<T> entityClass, Object entity, String uniquePropertyNames)
  {
    Assert.hasText(uniquePropertyNames);
    Criteria criteria = createCriteria(entityClass, new Criterion[0]).setProjection(Projections.rowCount());
    String[] nameList = uniquePropertyNames.split(",");
    try
    {
      for (String name : nameList) {
        criteria.add(Restrictions.eq(name, PropertyUtils.getProperty(entity, name)));
      }

      String idName = getIdName(entityClass);

      Serializable id = getId(entityClass, entity);

      if (id != null)
        criteria.add(Restrictions.not(Restrictions.eq(idName, id)));
    } catch (Exception e) {
      ReflectionUtils.handleReflectionException(e);
    }
    return ((Integer)criteria.uniqueResult()).intValue() == 0;
  }

  public Serializable getId(Class entityClass, Object entity)
    throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
  {
    Assert.notNull(entity);
    Assert.notNull(entityClass);
    return (Serializable)PropertyUtils.getProperty(entity, getIdName(entityClass));
  }

  public String getIdName(Class clazz)
  {
    Assert.notNull(clazz);
    ClassMetadata meta = getSessionFactory().getClassMetadata(clazz);
    Assert.notNull(meta, "Class " + clazz + " not define in hibernate session factory.");
    String idName = meta.getIdentifierPropertyName();
    Assert.hasText(idName, clazz.getSimpleName() + " has no identifier property define.");
    return idName;
  }

  private static String removeSelect(String hql)
  {
    Assert.hasText(hql);
    int beginPos = hql.toLowerCase().indexOf("from");
    Assert.isTrue(beginPos != -1, " hql : " + hql + " must has a keyword 'from'");
    return hql.substring(beginPos);
  }

  private static String removeOrders(String hql)
  {
    Assert.hasText(hql);
    Pattern p = Pattern.compile("order\\s*by[\\w|\\W|\\s|\\S]*", 2);
    Matcher m = p.matcher(hql);
    StringBuffer sb = new StringBuffer();
    while (m.find()) {
      m.appendReplacement(sb, "");
    }
    m.appendTail(sb);
    return sb.toString();
  }
  public void saveOrUpdate(Object o) {
    getHibernateTemplate().merge(o);
  }

  public void update(Object o)
  {
    getHibernateTemplate().update(o);
  }

  public Serializable saveAndgetPk(Object o)
  {
    try
    {
      Serializable pk = getHibernateTemplate().save(o);
      return pk;
    }
    catch (DataAccessException e) {
      e.printStackTrace();
    }
    return Integer.valueOf(-1);
  }
}